import {isDef} from "../../shared/util";
import {popTarget, pushTarget} from "./dep";
import {queueWatcher} from "./scheduler";

let watcherId = 0;

export class Watcher {

    /**
     * 创建一个 Watcher
     * @param {Reactive} vm ViewModel 实例
     * @param {Function} before watcher run 执行之前的动作
     * @param {Function} run watcher 的 run 函数，一般是渲染函数，计算表达式，或者设置值
     * @param {Function} after watcher run 执行之后的动作
     * @param options 同步标志，如果设为 true, 在 update 时时直接更新页面，不会进入渲染队列
     */
    constructor(vm, before, run, after, ...options) {
        this.id = watcherId ++;
        this.run = run;
        this.before = before;
        this.after = after;

        this.sync = isDef(options[0]) ? options[0] : false;

        this.deps = [];
        this.depSet = new Set();
        this.newDeps = [];
        this.newDepSet = new Set();

        // 立即更新一次
        this.updateImmediate();
    }

    depend (dep) {
        if (!this.newDepSet.has(dep.id)) {
            this.newDepSet.add(dep.id);
            this.newDeps.push(dep);
        }
    }

    cleanUpDeps () {
        // 清除 newDepSet 中不存在的 dep
        // 每次依赖的可能不一样，清除旧的依赖
        let index = this.deps.length - 1;
        while (index > 0) {
            const dep = this.deps[index];
            const depId = dep.id;
            if (!this.newDepSet.has(depId)) {
                this.deps.splice(index, 1);
            }
            index --;
        }

        // 交换用临时变量
        let temp = null;
        // 交换 deps 与 newDeps
        temp = this.deps;
        this.deps = this.newDeps;
        this.newDeps = temp;
        // 交换 depSet 与 newDepSet
        temp = this.depSet;
        this.depSet = this.newDepSet;
        this.newDepSet = temp;
    }

    updateImmediate () {
        pushTarget(this);
        if (isDef(this.before)) {
            this.before.call(this.vm);
        }
        if (isDef(this.run)) {
            this.run.call(this.vm);
            this.cleanUpDeps();
        }
        if (isDef(this.after)) {
            this.after.call(this.vm);
        }
        popTarget();
    }

    update () {
        if (this.sync) {
            this.updateImmediate();
        }
        else {
            queueWatcher(this);
        }
    }

}
